/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.paa;

import java.util.Arrays;

import mosdi.fa.CDFA;
import mosdi.util.ProductEncoder;

/** DAA that jointly handles sequence and sequence annotation and computes the
 *  number of matches and the annotation sum at match positions, i.e. for each match, 
 *  the sum of annotations covered by this match are counted. Therefore computed values
 *  cannot be interpreted directly, but can be decoded by valueToMatchCount() and
 *  valueToAnnotationSum() 
 */
public class MatchAnnotationDAA extends DAA {

	private ProductEncoder productAlphabet;
	private CDFA cdfa;
	private int valueCount;
	private int maxMatchCount;
	private int maxAnnotationSum;
	private int stateCount;
	private int patternLength;
	private ProductEncoder stateEncoder;
	private ProductEncoder valueEncoder;
	private int annotationAlphabetSize; 
	
	/** Constructor. */
	public MatchAnnotationDAA(int annotationAlphabetSize, CDFA cdfa, int patternLength, int maxMatchCount, int maxAnnotationSum) {
		this.productAlphabet = new ProductEncoder(cdfa.getAlphabetSize(), annotationAlphabetSize);
		this.annotationAlphabetSize = annotationAlphabetSize;
		this.cdfa = cdfa;
		this.valueCount = (maxMatchCount+1) * (maxAnnotationSum+1);
		this.valueEncoder = new ProductEncoder(maxMatchCount+1, maxAnnotationSum+1);
		this.maxAnnotationSum = maxAnnotationSum;
		this.maxMatchCount = maxMatchCount;
		this.patternLength = patternLength;
		long stateCount = cdfa.getStateCount();
		for (int i=0; i<patternLength; ++i) {
			stateCount *= annotationAlphabetSize;
			if (stateCount>Integer.MAX_VALUE) throw new IllegalArgumentException("State count too large (>Integer.MAX_VALUE).");
		}
		this.stateCount = (int)stateCount;
		int[] dimensions = new int[patternLength+1];
		Arrays.fill(dimensions, annotationAlphabetSize);
		dimensions[0] = cdfa.getStateCount();
		this.stateEncoder = new ProductEncoder(dimensions);
	}

	@Override
	public int getAlphabetSize() {
		return cdfa.getAlphabetSize() * annotationAlphabetSize;
	}

	@Override
	public int getEmissionCount() {
		return 1;
	}

	@Override
	public int getEmission(int state) {
		return 0;
	}

	@Override
	public int getStartState() {
		int[] state = new int[patternLength+1];
		state[0] = cdfa.getStartState(); 
		return stateEncoder.encode(state);
	}

	@Override
	public int getStartValue() {
		return valueEncoder.encode(0,0);
	}

	@Override
	public int getStateCount() {
		return stateCount;
	}

	@Override
	public int getValueCount() {
		return valueCount;
	}

	public int valueToMatchCount(int value) {
		return valueEncoder.decode(value)[0];
	}
	
	public int valueToAnnotationSum(int value) {
		return valueEncoder.decode(value)[1];
	}
	
	public int getValue(int matchCount, int annotationSum) {
		return valueEncoder.encode(matchCount, annotationSum);
	}
	
	@Override
	public int performOperation(int state, int value, int emission) {
		int[] decodedState = stateEncoder.decode(state);
		int output = cdfa.getStateOutput(decodedState[0]);
		// value[0]: match count
		// value[1]: annotation sum
		int[] values = valueEncoder.decode(value);
		if (output>0) {
			int sum = 0;
			for (int i=1; i<=patternLength; ++i) {
				sum += decodedState[i];
			}
			values[0] = Math.min(maxMatchCount, values[0]+output);
			values[1] = Math.min(maxAnnotationSum, values[1]+output*sum);
		}
		return valueEncoder.encode(values);
	}

	@Override
	public int getTransitionTarget(int state, int character) {
		int[] decodedState = stateEncoder.decode(state);
		// perform CDFA transition
		decodedState[0] = cdfa.getTransitionTarget(decodedState[0], productAlphabet.decodeComponent(0,character));
		// shift annotations
		for (int i=patternLength; i>1; --i) {
			decodedState[i] = decodedState[i-1];
		}
		decodedState[1] = productAlphabet.decodeComponent(1,character);
		return stateEncoder.encode(decodedState);
	}
	
}
